Spin
是一個載入狀態元件,當頁面正在處理非同步行為,或需要讓用戶等待的作業時,用來顯示以緩解用戶等待的焦慮。
一個簡單的 loading 狀態,只需要拿一個適合的 icon 來旋轉就可以了
Spin 元件雖然相對單純,但是也是有一些做法可以讓我們使用起來更為方便。
內容加載中
Antd 讓 Spin 也能夠擁有 children 元件,這樣的做法可以讓載入的同時也能夠看到內容。
<Spin spinning={this.state.spinning}>
{children}
</Spin>
假設一個情境是我們需要編輯某個資料 table 或是留言板,每做一個小動作就要打一次 API ,每次打 API 就會整頁白掉變成 loading 狀態,載入完之後再重新顯示資料,如果這些操作很頻繁的話,那這樣的使用者體驗必定很不好。
因此若這一頁的內容正在載入中,但是又不想要整個區塊換成載入狀態,或許用這種內容加載的方式也是一種選擇。
組建大小
透過一個 props 來決定 Spin 的大小,也是很基本卻很重要的性質之一,畢竟把 Spin 放在一顆按鈕裡面,跟把 Spin 放在整個頁面的中間,所需要的 size 很可能會不同。Antd 這邊只提供可選的選項,分別是 small
, default
, large
。
indicator
隨著網站的不同,為了配合設計師的設計,我們也需要能夠隨心所欲的更改 Spin 的 icon。當然一個網站應該不太會設計成每一頁的 Spin 都長不一樣,不過 Antd 是為了讓廣大的開發者能夠使用,因此會有這個需求,如果是自己的網站要使用,應該一到兩種應該就很夠用了。
屬性 | 說明 | 類型 | 默認值 |
---|---|---|---|
className | 客製化樣式 | string | |
isLoading | 是否載入中 | boolean | false |
indicator | 自定義載入符號 | ReactNode , string |
<CircularProgress /> |
children | 內容 | ReactNode , string |
Spin
最簡單的原理就是根據 是否加載中
這個 boolean 來判斷是否要顯示載入符號,其他的部分就是根據不同情境來調整樣式。
那今天我們要實作的情境有兩種,一種是 Spin 直接當作加載元件,一種是 Spin 會成為一個加載容器,所以我的想法如下:
const Spin = ({
indicator, isLoading, children, ...props
}) => {
if (!children) {
return indicator;
}
return (
<SpinContainer {...props}>
{children}
{isLoading && indicator}
</SpinContainer>
);
};
主要核心的想法到目前為止就已經差不多了,其他的部分就是樣式上的調整。
當我們要直接 show 出一個 Spin:
<Spin />
我預設的樣式就會是這樣:
Custom Indicator
當然這個樣式我偷懶是直接拿 MUI 的 <CircularProgress />
來使用。
如果我們要客製化樣式也是可以的,假設我們今天拿到一個 SVG 檔,我把它轉換成 JS:
export const SpinnerIcon = (props) => (
<svg
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="spinner"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="svg-inline--fa fa-spinner fa-w-16 fa-3x"
{...props}
>
<path fill="currentColor" d="M304 48c0 ...." className="" />
</svg>
);
然後我就能夠自己做一個 indicator,當作 props 傳入,或是直接做進 Spin
元件當作預設的 indicator,我這邊以 props 傳入為例:
import styled, { keyframes } from 'styled-components';
const rotateAnimation = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const RotateContainer = styled.div`
width: 40px;
height: 40px;
animation: ${rotateAnimation} 1000ms ease-in-out infinite;
`;
<Spin
indicator={(
<RotateContainer>
<FaSpinner />
</RotateContainer>
)}
/>
就會是這樣:
Spin As Container
再來就是我們把 Spin
當作一個容器,讓他的 children 有載入狀態,使用起來如下:
<Spin isLoading>
<Content />
</Spin>
我的作法提供給大家參考:
<SpinContainer {...props}>
{children}
{isLoading && (
<>
<Mask />
<Indicator
ref={indicatorRef}
className="spin__indicator"
$indicatorSize={indicatorSize}
>
{indicator}
</Indicator>
</>
)}
</SpinContainer>
主要的想法就是 <SpinContainer />
這一層設為 position: relative;
,然後裡面的 <Indicator />
設為 position: absolute;
讓他能夠蓋在內容上面,並做置中的定位。
然後可以看到我這邊多給一個 <Mask />
元件,主要是如果直接 show 出 indicator 疊在內容上的話,會顯得有點繚亂,所以我想要弱化載入中時內容的顯眼度,來凸顯加載中的樣式,以下就是今天的成果展示:
Spin 元件原始碼:
Source code
Storybook:
Spin